package org.eclipse.epsilon.emc.lom;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.epsilon.commons.util.StringProperties;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.exceptions.models.EolEnumerationValueNotFoundException;
import org.eclipse.epsilon.eol.exceptions.models.EolModelElementTypeNotFoundException;
import org.eclipse.epsilon.eol.exceptions.models.EolModelLoadingException;
import org.eclipse.epsilon.eol.exceptions.models.EolNotInstantiableModelElementTypeException;
import org.eclipse.epsilon.eol.execute.introspection.IPropertyGetter;
import org.eclipse.epsilon.eol.execute.introspection.IPropertySetter;
import org.eclipse.epsilon.eol.models.IModel;
import org.eclipse.epsilon.eol.models.transactions.IModelTransactionSupport;
import org.eclipse.epsilon.eol.models.transactions.NoModelTransactionSupport;
import org.w3c.dom.views.AbstractView;


public class LiveObjectModel implements IModel {
	
	protected String dbNamePath = null ;
	protected List<EPackage> ePackages = new ArrayList<EPackage>();
	private Statement stmt = null;
	private PreparedStatement pstmt = null;
	protected String name;

	public LiveObjectModel() {
		
	}
	
	public LiveObjectModel(String dbNamePath) {
		this.dbNamePath = dbNamePath;
	}
	
	public static void main(String[] args) throws Exception {
		
		LiveObjectModel m = new LiveObjectModel("E:/MSc_IT/Spring term/swi/projects/org.eclipse.epsilon.emc.lom/db/test");
		EPackage metamodel = new EmfWorkbench().registerMetamodel();
		m.addEPackage(metamodel);
		
		LiveObject library = m.getAllOfType("Book").get(0);
		
		System.out.println(library.className + library.id);
		
		LiveObject x = new LiveObject(3, "Book", m);
		List<LiveObject> o =  (List<LiveObject>) x.get("writers");
		System.out.println(o);
		
		
		boolean a = m.isOfKind(x, "Book");
		System.out.println(a);
		
	}
	
	public void addEPackage(EPackage ePackage) {
		ePackages.add(ePackage);
	}
	
	protected EClass classForName(String clazz) {
		for (EPackage ePackage : ePackages) {
			Iterator it = ePackage.eAllContents();
			while (it.hasNext()) {
				Object candidate = it.next();
				if (candidate instanceof EClass) {
					EClass eClass = (EClass) candidate;
					
					if (eClass.getName().equals(clazz)) {
						return eClass;
					}
				}
			}	
		}
		return null;
	}
	
	protected ArrayList<EClass> loadAllEClasses(){
		
		//Stores all the classes of a package
		ArrayList<EClass> allClasses = new ArrayList<EClass>();
		
		for (EPackage ePackage : ePackages) {
			Iterator it = ePackage.eAllContents();
			while (it.hasNext()) {
				Object candidate = it.next();
				if (candidate instanceof EClass) {
					EClass eClass = (EClass) candidate;
					
					allClasses.add(eClass);
				}
			}	
		}
		
		return  allClasses;
	}
	
	protected List<EClass> getAllSubTypes(String clazz) {
		
		//Stores the sub Types of a particular class( maybe declare it globally)
		ArrayList<EClass> allSubTypes = new ArrayList<EClass>();
		
		//Stores all the classes of a package ( maybe declare it globally)
		ArrayList<EClass> allClasses = new ArrayList<EClass>();
		
		allClasses = loadAllEClasses();
		
		EClass eClass = classForName(clazz);
		
		EClass current = eClass;
		
		//adds the Class itself in the subTypes ArrayList
		allSubTypes.add(eClass);
		
		int j = 0;
		
		while(j < allSubTypes.size()){
		
			
			for(int i = 0; i < allClasses.size(); i++){
				
				//checks if super type and also not to be the same class itsself
				if(current.isSuperTypeOf(allClasses.get(i)) && current != allClasses.get(i)){
					
					allSubTypes.add(allClasses.get(i));
				}
			}
			j = j + 1;
			
			if(j < allSubTypes.size()){
				current = allSubTypes.get(j);
			}
			
		}
		
		return allSubTypes;
	}
	
	
	
	public List<LiveObject> getAllOfKind(String type) {
		
		List<LiveObject> x = new ArrayList<LiveObject>();
		ArrayList<EClass> allSubTypes = new ArrayList<EClass>();
		
		allSubTypes = (ArrayList<EClass>) getAllSubTypes(type);
		
		for (int i = 0; i < allSubTypes.size(); i++){
			
			x.addAll(getAllOfType(allSubTypes.get(i).getName()));
		}
		
		
		return x;
	}
	
	public boolean owns(Object o) { return false; }
	

	
	
	public List<LiveObject> getAllOfType(String type){
		
		//sql to select data
		String sqlString= "SELECT ObjectId FROM Objects WHERE eClassName = '" + type + "'";
		
		List<LiveObject> x = new ArrayList<LiveObject>();
				
		try {
			//instantiate statement
			stmt = getConnection().createStatement();			
			
			//declare and populate result set
			ResultSet rs = 	stmt.executeQuery(sqlString);
			
			//local variables
			int id;
			
			while (rs.next()) {
				
				//get row values - can use column index or name
				id = rs.getInt(1);
				
				//use row values to add object to list
				x.add(new LiveObject(id, type, this));
				
			}
			
			//close statement
			stmt.close();
			
			
		} catch(SQLException s) {
			//inform user of error
			System.out.println("\n\nError in retrieving data:\n" + s.getMessage());
		
		} catch(Exception e){
			//inform user of error
			System.out.println("\n\nGeneral Error in retrieving data:\n" + e.getMessage());
			
		} finally {
			//close the connection
			//closeConnection();
		}
	 
		return x;
 	}
	
	public LiveObject getLiveObjectById(int id){
		
		//sql to select data
		String sqlString= "SELECT eClassName FROM Objects WHERE objectID = '" + id + "'";
		
		LiveObject x = null;
				
		try {
			//instantiate statement
			stmt = getConnection().createStatement();			
			
			//declare and populate result set
			ResultSet rs = 	stmt.executeQuery(sqlString);
			
			//local variables
			String name;
			
			while (rs.next()) {
				
				//get row values - can use column index or name
				name = rs.getString(1);
				
				//use row values to add object to list
				x = new LiveObject(id, name, this);
				
			}
			
			//close statement
			stmt.close();
			
			
		} catch(SQLException s) {
			//inform user of error
			System.out.println("\n\nError in retrieving data:\n" + s.getMessage());
		
		} catch(Exception e){
			//inform user of error
			System.out.println("\n\nGeneral Error in retrieving data:\n" + e.getMessage());
			
		} finally {
			//close the connection
			//closeConnection();
		}
	 
		return x;
 	}
	
	protected List<LiveObject> getLiveObject(int id, String property){
		//sql to select data
		String sqlString= "SELECT References.valueid, Objects.eclassname from Objects ,References " +
				"where Objects.objectid = References.objectid and " +
				" references.objectid = ? and references.referencename = ? " ;
		
		LiveObject x = null;
		
		ArrayList<LiveObject> a = new ArrayList<LiveObject>();		
		
		try {
			//instantiate statement
			pstmt = getConnection().prepareStatement(sqlString);
			
			pstmt.setInt(1, id);
			pstmt.setString(2, property);
			
			//declare and populate result set
			ResultSet rs = 	pstmt.executeQuery();
			
			//local variables
			int valueId;
			String eClassName ;
			
			while (rs.next()) {
				
				//get row values - can use column index or name
				valueId = rs.getInt(1);
				eClassName = rs.getString(2);
				
				//use row values to add object to list
				x = new LiveObject(valueId, eClassName, this);
				a.add(x);
			}
			
			//close statement
			stmt.close();
			
			
		} catch(SQLException s) {
			//inform user of error
			System.out.println("\n\nError in retrieving data:\n" + s.getMessage());
		
		} catch(Exception e){
			//inform user of error
			System.out.println("\n\nGeneral Error in retrieving data:\n" + e.getMessage());
			
		} finally {
			//close the connection
			//closeConnection();
		}
	 
		return a;
	}
	
	protected List<String> getAttributeValues(int id, String property){
		//sql to select data
		String sqlString= "SELECT attributeValue FROM Attributes WHERE objectID = ? AND attributeName = ?" ;
		
		List<String> x = new ArrayList<String>();
				
		try {
			//instantiate statement
			pstmt = getConnection().prepareStatement(sqlString);
			
			pstmt.setInt(1, id);
			pstmt.setString(2, property);
			
			//declare and populate result set
			ResultSet rs = 	pstmt.executeQuery();
			
			//local variables
			String value ;
			
			while (rs.next()) {
				
				//get row values - can use column index or name
				value = rs.getString(1);
				
				//use row values to add object to list
				x.add(value);
				
			}
			
			//close statement
			stmt.close();
			
			
		} catch(SQLException s) {
			//inform user of error
			System.out.println("\n\nError in retrieving data:\n" + s.getMessage());
		
		} catch(Exception e){
			//inform user of error
			System.out.println("\n\nGeneral Error in retrieving data:\n" + e.getMessage());
			
		} finally {
			//close the connection
			//closeConnection();
		}
	 
		return x;
	}
	
	protected Collection<Integer> getReferenceId(int id, String property){
		//sql to select data
		String sqlString= "SELECT ValueID FROM References WHERE objectID = ? AND referenceName = ?" ;
		
		Collection<Integer> x = new ArrayList<Integer>();
				
		try {
			//instantiate statement
			pstmt = getConnection().prepareStatement(sqlString);
			
			pstmt.setInt(1, id);
			pstmt.setString(2, property);
			
			//declare and populate result set
			ResultSet rs = 	pstmt.executeQuery();
			
			//local variables
			int value ;
			
			while (rs.next()) {
				
				//get row values - can use column index or name
				value = rs.getInt(1);
				
				//use row values to add object to list
				x.add(value);
				
			}
			
			//close statement
			stmt.close();
			
			
		} catch(SQLException s) {
			//inform user of error
			System.out.println("\n\nError in retrieving data:\n" + s.getMessage());
		
		} catch(Exception e){
			//inform user of error
			System.out.println("\n\nGeneral Error in retrieving data:\n" + e.getMessage());
			
		} finally {
			//close the connection
			//closeConnection();
		}
	 
		return x;
	}
	
	private Connection con = null;
	
	public Connection getConnection(){
		
		if (con == null) {
			try {
				//load software driver
				Class.forName("org.h2.Driver");
				
				if (dbNamePath.endsWith(".h2.db")) {
					dbNamePath = dbNamePath.substring(0, dbNamePath.length() - 6);
				}
				
				//open connection
				con = DriverManager.getConnection("jdbc:h2:" + dbNamePath, "sa", "");
				//con = DriverManager.getConnection("jdbc:h2:E:/Database/test", "sa", "");
				
				
			} catch (Exception e) { 
				e.printStackTrace(); 
			}			
		}
		
		return con;
	}
	
	private void closeConnection(){

		try {
			//close connection to db
			con.close();
	 
			//set con to null
			con = null;
			
		} catch (Exception e) { 
			e.printStackTrace(); 
		}
	}
	
	//TODO: Implement
	public Collection<?> allContents() {
		//sql to select data
		String sqlString= "SELECT * FROM Objects ";
		
		Collection<LiveObject> x = new ArrayList<LiveObject>();
				
		try {
			//instantiate statement
			stmt = getConnection().createStatement();			
			
			//declare and populate result set
			ResultSet rs = 	stmt.executeQuery(sqlString);
			
			//local variables
			int id;
			String type;
			
			while (rs.next()) {
				
				//get row values 
				id = rs.getInt(1);
				type = rs.getString(2);
				
				//use row values to add object to list
				x.add(new LiveObject(id, type, this));
				
			}
			
			//close statement
			stmt.close();
			
			
		} catch(SQLException s) {
			//inform user of error
			System.out.println("\n\nError in retrieving data:\n" + s.getMessage());
		
		} catch(Exception e){
			//inform user of error
			System.out.println("\n\nGeneral Error in retrieving data:\n" + e.getMessage());
			
		} finally {
			//close the connection
			//closeConnection();
		}
	 
		return x;
 	}
		
	

	public Object createInstance(String type)
			throws EolModelElementTypeNotFoundException,
			EolNotInstantiableModelElementTypeException {
		throw new EolNotInstantiableModelElementTypeException(getName(), type);
	}

	public Object createInstance(String type, Collection<Object> parameters)
			throws EolModelElementTypeNotFoundException,
			EolNotInstantiableModelElementTypeException {
		throw new EolNotInstantiableModelElementTypeException(getName(), type);
	}

	public void deleteElement(Object instance) throws EolRuntimeException {
		
	}

	//TODO: Implement
	public void dispose() {
		try {
			//close connection to db
			if (con != null) con.close();
	 
			//set con to null
			con = null;
			
		} catch (Exception e) { 
			e.printStackTrace(); 
		}
	}

	protected List<String> aliases = new ArrayList<String>();
	public List<String> getAliases() {
		return aliases;
	}

	//TODO: Implement
	public Object getElementById(String id) {
		
		//sql to select data
		String sqlString= "SELECT eClassName FROM Objects WHERE objectID = '" + id + "'";
		
		LiveObject x = null;
				
		try {
			//instantiate statement
			stmt = getConnection().createStatement();			
			
			//declare and populate result set
			ResultSet rs = 	stmt.executeQuery(sqlString);
			
			//local variables
			String name;
			
			while (rs.next()) {
				
				//get row values - can use column index or name
				name = rs.getString(1);
				
				//use row values to add object to list
				x = new LiveObject(Integer.parseInt(id), name, this);
				
			}
			
			//close statement
			stmt.close();
			
			
		} catch(SQLException s) {
			//inform user of error
			System.out.println("\n\nError in retrieving data:\n" + s.getMessage());
		
		} catch(Exception e){
			//inform user of error
			System.out.println("\n\nGeneral Error in retrieving data:\n" + e.getMessage());
			
		} finally {
			//close the connection
			//closeConnection();
		}
	 
		return x;
 	}
		
	
	
	//TODO: Implement
	public String getElementId(Object instance) {
		
		//Check if object is instance of an EClass
		if(instance instanceof EClass ){
			
			EClass eclass = (EClass) instance;
			String name  = eclass.getName();
			
			//sql to select data
			String sqlString= "SELECT objectID FROM Objects WHERE eClassName = '" + name + "'";
			
			int x = -1; 
					
			try {
				//instantiate statement
				stmt = getConnection().createStatement();			
				
				//declare and populate result set
				ResultSet rs = 	stmt.executeQuery(sqlString);
				
				
				while (rs.next()) {
					
					//get row values - can use column index or name
					x = rs.getInt(1);
					
					
				}
				
				//close statement
				stmt.close();
				
				
			} catch(SQLException s) {
				//inform user of error
				System.out.println("\n\nError in retrieving data:\n" + s.getMessage());
			
			} catch(Exception e){
				//inform user of error
				System.out.println("\n\nGeneral Error in retrieving data:\n" + e.getMessage());
				
			} finally {
				//close the connection
				//closeConnection();
			}
		 
			return Integer.toString(x);
	 	}
		else{
			return null;
		}
			
	}

	//Ignore
	public Object getEnumerationValue(String enumeration, String label)
			throws EolEnumerationValueNotFoundException {
		return null;
	}

	public String getName() {
		return name;
	}

	public IPropertyGetter getPropertyGetter() {
		return new LiveObjectPropertyGetter();
	}

	public IPropertySetter getPropertySetter() {
		// TODO Auto-generated method stub
		return null;
	}

	public IModelTransactionSupport getTransactionSupport() {
		return new NoModelTransactionSupport();
	}
	
	//TODO: Implement
	public String getTypeNameOf(Object instance) {
		
		if(instance instanceof LiveObject){
			LiveObject instance2= (LiveObject) instance;
			return instance2.className;
		}else{
			return null;
		}
			
	}

	//TODO: Return EClass of LiveObject
	public Object getTypeOf(Object instance) {
		
		if(instance instanceof LiveObject){
			LiveObject instance2= (LiveObject) instance;
			return classForName(instance2.className);
		}else{
			return null;
		}
	}
	
	//TODO: Implement with classForName
	public boolean hasType(String type) {
		
		if (classForName(type) == null){
			return false;
		}else{
			return true;
		}
		
		
	}

	public boolean isInstantiable(String type) {
		return false;
	}

	public boolean isModelElement(Object instance) {
		return instance instanceof LiveObject;
	}

	//TODO: Implement
	public boolean isOfKind(Object instance, String type)throws EolModelElementTypeNotFoundException {
		
		
		if(instance instanceof LiveObject){
			LiveObject instance2= (LiveObject) instance;
			//ArrayList<LiveObject> b = (ArrayList<LiveObject>) getAllOfKind(type);
			List<EClass> b = getAllSubTypes(type);
			
			for(EClass eClass : b){
				
				if(classForName(instance2.className) == eClass){
					return true;
				}else{
					return false;
				}
				
			}
		}
		
		return false;
		
		
	}

	//TODO: Implement
	public boolean isOfType(Object instance, String type)throws EolModelElementTypeNotFoundException {
		if(instance instanceof LiveObject){
			LiveObject instance2= (LiveObject) instance;
			if(instance2.className.equals(type)){
				return true;
			}else{
			
				return false;
			}
		}else{
			return false;
		}
	}

	public boolean isReadOnLoad() {
		return false;
	}

	public boolean isStoredOnDispoal() {
		return false;
	}

	//TODO: Implement
	public boolean knowsAboutProperty(Object instance, String property) {
		
		if(instance instanceof LiveObject){
			LiveObject instance2 = (LiveObject) instance;
			EClass eClass = classForName(instance2.className);
			
			EStructuralFeature sf = eClass.getEStructuralFeature(property);
			
			System.err.println(eClass.getEPackage().getNsURI());
			
			System.err.println("sf for " + property + " is " + sf);
			
			if(sf == null){
				return false;
			}else{
				return true;
			}
		}
		return false;
	}
	
	public static String PROPERTY_NAME = "name";
	public static String PROPERTY_ALIASES = "aliases";
	public static String PROPERTY_DBNAMEPATH = "dbNamePath";
	public static String PROPERTY_METAMODEL_URI = "metamodelUri";
	
	
	public void load(StringProperties properties, String basePath)
			throws EolModelLoadingException {
	
		this.name = properties.getProperty(PROPERTY_NAME);
		
		String[] aliases = properties.getProperty(PROPERTY_ALIASES).split(",");
		for (int i=0;i<aliases.length;i++){
			this.aliases.add(aliases[i].trim());
		}
		
		this.dbNamePath = properties.getProperty(PROPERTY_DBNAMEPATH);
		
		for (Object o : EPackage.Registry.INSTANCE.values()) {
			if (o instanceof EPackage) {
				EPackage p = (EPackage) o;
				if (p.getNsURI().equals(properties.getProperty(PROPERTY_METAMODEL_URI))) {
					addEPackage(p);
				}
			}
		}
		
	}

	public void load() throws EolModelLoadingException {}

	public void setElementId(Object instance, String newId) {}

	public void setName(String name) {
		this.name = name;
	}

	public void setReadOnLoad(boolean readOnLoad) {}

	public void setStoredOnDisposal(boolean storedOnDisposal) {}

	public boolean store(String location) {return false;}

	public boolean store() { return false; }
	
}

